package com.ayg.test.apitest.core;

import com.ayg.test.apitest.constants.YamlElements;
import com.ayg.test.apitest.dao.PaymentDao;
import com.ayg.test.apitest.dao.PrepareDataDao;
import com.ayg.test.apitest.entity.TestSuits;
import com.ayg.test.apitest.utils.*;
import com.esotericsoftware.yamlbeans.YamlException;
import com.ayg.test.apitest.entity.TestCase;
import com.ayg.test.apitest.entity.Request;
import com.ayg.test.apitest.entity.Validate;
import com.esotericsoftware.yamlbeans.YamlReader;
import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import io.restassured.RestAssured;
import io.restassured.config.EncoderConfig;
import io.restassured.config.RestAssuredConfig;
import io.restassured.parsing.Parser;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.annotations.*;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BaseEngine {
    private static final Logger logger = LoggerFactory.getLogger(BaseEngine.class);
    //公共参数数据池（全局可用）
    protected static Map<String, String> saveDatas = new HashMap<>();
    protected static Map cookies = new HashMap();
    protected PaymentDao dao = new PaymentDao();
    protected  PrepareDataDao prepareDataDao = new PrepareDataDao();

    //替换符，如果数据中包含“${}”则会被替换成公共参数中存储的数据
    protected Pattern replaceParamPattern = Pattern.compile("\\$\\{(.*?)\\}");

    //截取自定义方法正则表达式：__xxx(ooo)
    protected Pattern funPattern = Pattern
            .compile("__(\\w*?)\\((([\\w\\\\\\/:\\.\\$]*,?)*)\\)");

    public String TESTCASE_PATH = "/payment";
    public static final String SEPARATOR = "\\";
    public static final String API_CONFIG = "/api_config.yaml";
    private static final String DATA_PROVIDER_DIR= "envdir";
    protected String project;
    protected String saveToDB;
    protected String env = "test" ;

    //api测试用例集
    List<TestCase> testCases = new ArrayList<>();
    List beforeSuit = new ArrayList();
    List afterSuit = new ArrayList();

    @BeforeSuite
    public void init() throws Exception {
        //读取全局配置文件，初始化数据
        String global = getClass().getResource(API_CONFIG).getPath();
        YamlRead yamlRead = new YamlRead(global);
        Map apiConfig = (Map)yamlRead.getYmal();
        env = (String)apiConfig.get("env");
        logger.info("获取执行环境：{}",env);
        //加载配置文件
        String path = "/env/"+env;
        List<String> configFiles = TestFileReader.readfile(path);
        for(String configPath : configFiles) {
            YamlReader reader = new YamlReader(new FileReader(configPath));
            Map<String,String> config = reader.read(Map.class);
            logger.info("配置的条数为：{}",config.size());
            saveDatas.putAll(config);
        }

    }

    @BeforeTest
    public void readData(ITestContext context) throws YamlException, FileNotFoundException {
        logger.info("beforeTest==============================");
        String casePath = context.getCurrentXmlTest().getParameter("case_path");
        if(StringUtil.isNotEmpty(casePath)) {
            TESTCASE_PATH = casePath;
        }
        String projectFromXml = context.getCurrentXmlTest().getParameter("project");
        if(StringUtil.isNotEmpty(projectFromXml)) {
            project = projectFromXml;
        }
        String saveToDBFromXml = context.getCurrentXmlTest().getParameter("saveToDB");
        if(StringUtil.isNotEmpty(saveToDBFromXml)) {
            saveToDB = saveToDBFromXml;
        }
        String evnFromXml = context.getCurrentXmlTest().getParameter("env");
        if(StringUtil.isNotEmpty(evnFromXml)){
            env = evnFromXml;
        }
        List<String> testcaseFiles = TestFileReader.readfile(TESTCASE_PATH);
        for(String path : testcaseFiles) {
            logger.info("file seperator:"+File.separator);
            if(path.endsWith(".yml") || path.endsWith(".yaml")) {
                getTestCases(path);
            }else if(path.endsWith(".json")){
                //读取json格式用例文件
               getTestcaseFromJson(path);
            }
        }
        //处理beforeSutie
        logger.info("处理beforeSutie");
        dealSuitOfSql(beforeSuit);
        RestAssured.defaultParser = Parser.JSON;
        RestAssuredConfig raConfig = new RestAssuredConfig();
        RestAssured.config = raConfig.encoderConfig(new EncoderConfig("UTF-8", "UTF-8"));
    }

    @AfterSuite
    public void finish() throws Exception {
        logger.info("afterSuit==============================");
    }

    @AfterTest
    public void afterTest() throws Exception {
        logger.info("afterTest==============================");
        //处理beforeSutie
        logger.info("处理afterSutie");
        dealSuitOfSql(afterSuit);
    }

    @AfterMethod
    public void afterMethod() throws Exception {
        logger.info("afterMethod==============================");
    }

    protected String getCommonParam(String param) {
        Matcher m = replaceParamPattern.matcher(param);// 取公共参数正则
        while (m.find()) {

            String replaceKey = m.group(1);
            String value;
            // 从公共参数池中获取值
            value = getSaveData(replaceKey);
            // 如果公共参数池中未能找到对应的值，该用例失败。
            logger.info("进行公共参数替换:{},{}",param,value);
            Assert.assertNotNull(value,
                    String.format("格式化参数失败，公共参数中找不到%s。", replaceKey));
            param = param.replace(m.group(), value);
        }
        return param;
    }


    //获取公共数据池中的数据
    protected String getSaveData(String key) {
        if ("".equals(key) || !saveDatas.containsKey(key)) {
            return null;
        } else {
            return saveDatas.get(key);
        }
    }

    //组件预参数（处理__fucn()以及${xxxx}）
    protected String buildParam(String param) {
        // 处理公共数据
        if(!param.startsWith("__")) {
            param = getCommonParam(param);
        }else {
            //处理预定义方法调用__func
            Matcher m = funPattern.matcher(param);
            while (m.find()) {
                String funcName = m.group(1);
                String args = m.group(2);
                String value;
                // bodyfile属于特殊情况，不进行匹配，在post请求的时候进行处理
                if (FunctionUtil.isFunction(funcName)
                        && !funcName.equals("bodyfile")) {
                    // TODO 处理数据库操作函数 ,仅支持单值查询 begin
                    if(funcName.startsWith("db")){
                        args = getCommonParam(param);
                        args = args +","+ env;
                    }
                    // TODO 处理数据库操作函数 end
                    // 属于函数助手，调用那个函数助手获取。
                    value = FunctionUtil.getValue(funcName, args.split(","));
                    logger.info("进行自定义方法调用并替换:{},{}",param,value);
                    // 解析对应的函数失败
                    Assert.assertNotNull(value,
                            String.format("解析函数失败：%s。", funcName));
                    param = StringUtil.replaceFirst(param, m.group(), value);
                }
            }
        }
        return param;
    }

    TestCase testCaseConfig = new TestCase();
    //获取测试用例数据
    public void getTestCases(String path) throws YamlException, FileNotFoundException {
        logger.info("读取测试用例:{}",path);
        YamlRead yamlRead = new YamlRead(path);
        List<Map>  casesInFile = (List<Map>) yamlRead.getYmal();

        for (Map tc : casesInFile) {
            Map config = (Map)tc.get(YamlElements.CONFIG);

            if(config != null){
                List tempBeforeSuit = (List) config.get("beforeSuit");
                if(tempBeforeSuit != null){
                    beforeSuit.addAll(tempBeforeSuit);
                }
                List tempAfterSuit = (List) config.get("afterSuit");
                if(tempAfterSuit != null){
                    afterSuit.addAll(tempAfterSuit);
                }
                Map requestMap = (Map)config.get(YamlElements.REQUEST);
                Request request = new Request();
                String baseUrl = (String) requestMap.get(YamlElements.BASE_URL);
                if(StringUtil.isNotEmpty(baseUrl))
                     request.setBaseUrl(buildParam(baseUrl));
                request.setHeaders((Map) requestMap.get(YamlElements.HEADERS));
                request.setJson((Map) requestMap.get(YamlElements.JSON));
                request.setCookies((Map)requestMap.get(YamlElements.COOKIES));
                List variablesList = (List)config.get(YamlElements.VARIABLES);

                if (variablesList != null) {
                    addVariablesToGlobal(variablesList);
                }
                testCaseConfig.setRequest(request);
                testCaseConfig.setVariables(variablesList);
            }
            Map common = (Map)tc.get(YamlElements.COMMON);
            if(common != null){
                //读取通用的Case
                String commonCasePath = (String)common.get("path");
                commonCasePath = getClass().getResource(commonCasePath).getPath();
                getTestCases(commonCasePath);
            }
            Map<String,Object> item = (Map)tc.get(YamlElements.TEST);
            if(item != null) {
                String validateDataPath =  (String)item.get("validateData");
                TestCase testCase = new TestCase();
                Map requestMap = (Map) item.get(YamlElements.REQUEST);
                String dataPath = (String)requestMap.get("data");

                testCase.setName((String)item.get(YamlElements.NAME));
                String typeStr = (String)item.get(YamlElements.TYPE);
                testCase.setType(StringUtil.isNotEmpty(typeStr)?Integer.valueOf(typeStr):1);
                List beforeActionList = (List)item.get("beforeAction");
                if(beforeActionList!=null && beforeActionList.size()>0) {
                    testCase.getBeforeAction().addAll(beforeActionList);
                }
                List afterActionList = (List) item.get("afterAction");
                if(afterActionList!=null && afterActionList.size()>0) {
                    testCase.getAfterAction().addAll(afterActionList);
                }
                Request request = new Request();
                String baseUrl = "";
                if(testCaseConfig != null){
                    testCase.setVariables(testCaseConfig.getVariables());
                    if(testCaseConfig.getRequest().getHeaders()!= null) {
                        if(testCaseConfig.getRequest().getHeaders()!=null) {
                            request.getHeaders().putAll(testCaseConfig.getRequest().getHeaders());
                        }
                        if(testCaseConfig.getRequest().getJson()!=null) {
                            request.getJson().putAll(testCaseConfig.getRequest().getJson());
                        }
                        if(testCaseConfig.getRequest().getCookies()!=null){
                            request.getCookies().putAll(testCaseConfig.getRequest().getCookies());
                        }
                    }
                    if(StringUtil.isNotEmpty(testCaseConfig.getRequest().getBaseUrl())) {
                        baseUrl = testCaseConfig.getRequest().getBaseUrl();
                    }
                }
                String testBaseUrl = (String)requestMap.get(YamlElements.BASE_URL);
                if(StringUtil.isNotEmpty(testBaseUrl)){
                    baseUrl = testBaseUrl;
                }
                request.setMethod((String)requestMap.get(YamlElements.METHOD));
                if(StringUtil.isNotEmpty((String)requestMap.get(YamlElements.PARAM_TYPE))){
                    request.setParamType((String)requestMap.get(YamlElements.PARAM_TYPE));
                }
                Map headers = (Map)requestMap.get(YamlElements.HEADERS);
                if(headers!=null){
                    request.getHeaders().putAll(headers);
                }
                Map jsons = (Map)requestMap.get(YamlElements.JSON);
                if(jsons!=null){
                    request.getJson().putAll(jsons);
                }
                Map cookies = (Map)requestMap.get(YamlElements.COOKIES);
                if(cookies!=null){
                    request.getCookies().putAll(cookies);
                }
                request.setUrl(baseUrl+(String) requestMap.get(YamlElements.URL));
                List extractList = (List) item.get(YamlElements.EXTRACT);
                List validateRawList = (List) item.get(YamlElements.VALIDATE);
                List validateList = new ArrayList();
                for (Object o : validateRawList) {
                    Map map = (Map) o;
                    Validate validate = new Validate();
                    validate.setComparator((String) map.get(YamlElements.COMPARATOR));
                    validate.setCheck((String) map.get(YamlElements.CHECK));
                    validate.setExpect((String) map.get(YamlElements.EXPECT));
                    if(map.get(YamlElements.SQL)!=null){
                        validate.setSql((String) map.get(YamlElements.SQL));
                    }
                    validateList.add(validate);
                }
                testCase.setRequest(request);
                testCase.setExtract(extractList);
                testCase.setValidate(validateList);
                List dataProviderList = null;
                Map<Integer,List> validateDataMap = null;
              //csv文件数据驱动
                if(StringUtil.isNotEmpty(dataPath)){
                    dataPath = dataPath.replace(DATA_PROVIDER_DIR,env);
                    logger.info("读取数据驱动文件:{}",dataPath);
                    dataProviderList = CsvUtil.readCSV(dataPath);
                    if(StringUtil.isNotEmpty(validateDataPath)){
                        validateDataPath = validateDataPath.replace(DATA_PROVIDER_DIR,env);
                        validateDataMap = CsvUtil.readValidateDataFile(validateDataPath);
                    }
                }
                if(dataProviderList != null){
                    logger.info("组装数据参数");
                    try {
                        int index = 0;
                        for(Object paramsObj : dataProviderList){
                            Map paramsMap = (Map)paramsObj;
                            TestCase testCaseByParam = new TestCase();
                            BeanUtils.copyProperties(testCaseByParam, testCase);
                            Map requestParams = new HashMap();
                            requestParams.putAll(testCase.getRequest().getJson());
                            Request requestByParam = new Request();
                            BeanUtils.copyProperties(requestByParam, testCaseByParam.getRequest());
                            requestByParam.setJson(requestParams);
                            testCaseByParam.setRequest(requestByParam);
                            testCases.add(testCaseByParam);
                            //验证器参数化
                            if(validateDataMap != null && validateDataMap.size()>0) {
                                List<String> dynaValidateData = new ArrayList<>();
                                dynaValidateData.addAll(validateList);
                                dynaValidateData.addAll(validateDataMap.get(index++));
                                testCaseByParam.setValidate(dynaValidateData);
                            }
                            String key = (String)paramsMap.get("inner");
                            if(key != null){
                                //内层嵌套参数化，需要"inner"关键字来识别
                                paramsMap.remove("inner");
                                Object innerParam = testCase.getRequest().getJson().get(key);
                                if(innerParam != null){
                                    //只添加部分字段
                                    Map innerMap = new HashMap();
                                    Map innerParamMap = (Map)innerParam;
                                    for(Object keyObj :innerParamMap.keySet()){
                                        innerMap.put(keyObj,innerParamMap.get(keyObj));
                                    }
                                    innerMap.putAll(paramsMap);
                                    requestParams.put(key,innerMap);
                                    requestByParam.setJson(requestParams);
                                    testCaseByParam.setRequest(requestByParam);
                                }else {
                                    //全部字段
                                    requestParams.put(key,paramsMap);
                                }
                            }else {
                                //最外层参数化
                                requestParams.putAll((Map) paramsObj);
                            }
                        }
                    } catch (Exception e) {
                        logger.info("拷贝javaBean:TestCase失败");
                        logger.error("{}",e);
                    }
                }else{
                    testCases.add(testCase);
                }
            }
        }
    }

    public  void getTestcaseFromJson(String path) {
        if (!path.endsWith(".json")) {
            return;
        }
        String content = TestFileReader.readFileContent(path);
        Gson gson = new Gson();
        TestSuits testSuits = gson.fromJson(content, TestSuits.class);
        List<TestCase> testcaseList = testSuits.getTestCaseList();
        if (testSuits.getConfig() != null) {
            testCaseConfig = testSuits.getConfig();
        }
        for (TestCase testCase : testcaseList) {
            if (StringUtil.isNotEmpty(testCase.getCommon())) {
                getTestcaseFromJson(this.getClass().getResource(testCase.getCommon()).getPath());
            } else{
                if (testCaseConfig != null) {
                    //testCase.setVariables(testCaseConfig.getVariables());
                    Request request = CloneUtils.clone(testCaseConfig.getRequest());
                    if (testCaseConfig.getRequest() != null) {
                        if (testCaseConfig.getRequest().getHeaders() != null) {
                            request.getHeaders().putAll(testCase.getRequest().getHeaders());
                        }
                        if (testCaseConfig.getRequest().getJson() != null) {
                            request.getJson().putAll(testCase.getRequest().getJson());
                        }
                        if (testCaseConfig.getRequest().getCookies() != null) {
                            request.getCookies().putAll(testCase.getRequest().getCookies());
                        }
                    }
                    request.setMethod(testCase.getRequest().getMethod());
                    request.setParamType(testCase.getRequest().getParamType());
                    request.setValidateData(testCase.getRequest().getValidateData());
                    request.setData(testCase.getRequest().getData());
                    request.setUrl(buildParam(request.getBaseUrl() + testCase.getRequest().getUrl()));
                    if (testCaseConfig.getVariables() != null) {
                        addVariablesToGlobal(testCaseConfig.getVariables());
                    }
                    testCase.setRequest(request);
                }
                String dataPath = testCase.getRequest().getData();
                String validateDataPath = testCase.getRequest().getValidateData();
                List dataProviderList = null;
                Map<Integer, List> validateDataMap = null;
                //csv文件数据驱动
                if (StringUtil.isNotEmpty(dataPath)) {
                    dataPath = dataPath.replace(DATA_PROVIDER_DIR,env);
                    dataProviderList = CsvUtil.readCSV(dataPath);
                    if (StringUtil.isNotEmpty(validateDataPath)) {
                        validateDataPath = validateDataPath.replace(DATA_PROVIDER_DIR,env);
                        validateDataMap = CsvUtil.readValidateDataFile(validateDataPath);
                    }
                }
                if (dataProviderList == null) {
                    testCases.add(testCase);
                    return;
                }
                int index = 0;
                for (Object paramsObj : dataProviderList) {
                    Map paramsMap = (Map) paramsObj;
                    TestCase cloneTestCase = CloneUtils.clone(testCase);

                    if (validateDataMap != null) {
                        cloneTestCase.getValidate().addAll(validateDataMap.get(index++));
                    }
                    String key = (String)paramsMap.get("inner");
                    if(key != null){
                        //内层嵌套参数化，需要"inner"关键字来识别
                        paramsMap.remove("inner");
                        Object innerParam = testCase.getRequest().getJson().get(key);
                        if(innerParam != null){
                            //只添加部分字段
                            Map innerParamMap = CloneUtils.clone((LinkedTreeMap)innerParam);
                            innerParamMap.putAll(paramsMap);
                            cloneTestCase.getRequest().getJson().put(key,innerParamMap);
                        }else {
                            //全部字段
                            cloneTestCase.getRequest().getJson().put(key,paramsMap);
                        }
                    }else {
                        //最外层参数化
                        cloneTestCase.getRequest().getJson().putAll(paramsMap);
                    }
                    testCases.add(cloneTestCase);
                }
            }
        }

    }

    private void addVariablesToGlobal(List variables) {
        for (Object variablesObj : variables) {
            Map<String,String> map = (Map) variablesObj;
            for (Map.Entry<String,String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                value = buildParam(value);
                saveDatas.put( key, value);
                map.put(key, value);
            }
        }
    }

    private void dealSuitOfSql(List beforeSuit) {
        if (beforeSuit != null) {
            for (Object obj : beforeSuit) {
                Map actionMap = (Map) obj;
                String prefix = (String) actionMap.get("prefix");
                String sql = (String) actionMap.get("sql");
                if ("select".equals(actionMap.get("type"))) {
                    Map<String,String> prepareDataMap = prepareDataDao.queryPrepareData(env,sql);
                    prepareDataMap.forEach((key,value) ->{
                        key = prefix+"."+key;
                        saveDatas.put(key,value);
                    });
                }else if("update".equals(actionMap.get("type"))){
                    prepareDataDao.update(env,sql);
                }
                logger.info("env:{},sql:{}", env, sql);
            }
        }
    }
}
